﻿using System;
using System.Text;

namespace BizElements.Core.ProviderComponents
{
    /// <summary>Renders SQL FROM clause.</summary>
    sealed class GenericFromClauseRenderer : IFromClauseRenderer
    {
        #region Render tables and relations in the same order as provided.

        /// <summary>
        /// Renders SQL FROM clause.
        /// </summary>
        /// <param name="from">FROM clause.</param>
        /// <param name="dbms">Target DBMS:</param>
        /// <param name="output">StringBuilder to which SQL is appended.</param>
        public void Render(FromClause from, DbmsType dbms, StringBuilder output)
        {
            AppendFromFirstTableNameAndAlias(from.FromTable, dbms, output);
            foreach (JoinClause join in from.Joins)
                RenderJoinTable(dbms, output, join.Relation, join.JoinedTable, join.IsLeftOuterJoin);
        }

        private static void AppendFromFirstTableNameAndAlias(IDbTable firstTable, DbmsType dbms, StringBuilder from)
        {
            from.Append("FROM ");
            firstTable.RenderTableName(dbms, from);
            if (firstTable.Alias != firstTable.TableName)
            {
                // AS is not used because some DBMSs don't support that syntax.
                from.Append(" ");
                firstTable.RenderAlias(dbms, from);
            }
        }

        private static void RenderJoinTable(DbmsType dbms, StringBuilder from, DbRelation currRelation, IDbTable currTable, bool useLeftJoin)
        {
            AppendJoinMode(from, useLeftJoin);
            AppendTableNameAndAlias(dbms, from, currTable);
            RenderOnStatement(currRelation, dbms, from);
        }

        private static void AppendJoinMode(StringBuilder from, bool useLeftJoin)
        {
            if (useLeftJoin)
                from.Append(" LEFT OUTER JOIN ");
            else
                from.Append(" INNER JOIN ");
        }

        private static void AppendTableNameAndAlias(DbmsType dbms, StringBuilder from, IDbTable currTable)
        {
            currTable.RenderTableName(dbms, from);
            from.Append(" ");
            if (currTable.Alias != currTable.TableName)
            {
                // AS is not used because some DBMSs don't support that syntax.
                from.Append(" ");
                currTable.RenderAlias(dbms, from);
                from.Append(" ");
            }
        }

        /// <summary>
        /// Renders SQL ON clause.
        /// Eg: 'ON Countries.Id = Cities.IdCountry'
        /// </summary>
        /// <param name="rel">Relation to render as ON clause.</param>
        /// <param name="dbms">Target DBMS.</param>
        /// <param name="output">StringBuilder to which ON statement is appended.</param>
        private static void RenderOnStatement(DbRelation rel, DbmsType dbms, StringBuilder output)
        {
            output.Append("ON ");
            for (int fieldIdxInRelation = 0; fieldIdxInRelation < rel.ParentPrimaryKey.Length; fieldIdxInRelation++)
            {
                if (fieldIdxInRelation > 0)
                    output.Append("AND ");

                rel.ParentPrimaryKey[fieldIdxInRelation].RenderFullName(dbms, output);
                output.Append(" = ");
                rel.ChildForeignKey[fieldIdxInRelation].RenderFullName(dbms, output);
                output.Append(" ");
            }
        }

        #endregion
    }
}